﻿namespace itau.im.gerador
{

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using itau.im.entidade;
    using itau.im.negocio;
    using EnterpriseLibraryWrapper;

    public class GeradorDeCurvaPMT : IGeradorDeCurva
    {

        #region Constantes

        private const int SITUACAO_LIBERADA = 40;
        private const int SITUACAO_EM_EXECUCAO = 500;

        private const int TIPOPRAZO_OVER = 4;
        private const int TIPOPRAZO_30 = 3;
        private const int TIPOPRAZO_360 = 2;
        private const int TIPOPRAZO_252 = 1;

        private const int TIPO_TAXA_EXPONENCIAL = 1;
        private const int TIPO_TAXA_LINEAR = 2;
        private const int TIPO_TAXA_FATOR_DIARIO = 3;
        private const int TIPO_TAXA_PU = 4;
        private const int TIPO_TAXA_CONSTANTE = 5;
        private const int TIPO_TAXA_CUBIC_SPLINE = 6;
        private const int TIPO_TAXA_TAXA = 7;

        private const int TIPO_PONTO_ANTEPOLACAO = 1;
        private const int TIPO_PONTO_INTERPOLACAO = 2;
        private const int TIPO_PONTO_EXTRAPOLACAO = 3;
        private const int TIPO_PONTO_VERTICE = 4;

        private const string MASCARA_DATA = "dd/MM/yyyy HH:mm:ss";

        private const int INDICADOR_VERTICE_SIM = 1;

        #endregion

        #region Atributos

        CalendarioReguaPMTBS _reguaCurva;
        RegistroExecucaoCurva _registroExecucaoCurvaBaseBullet;
        Curva _curvaBaseBullet;
        IList<CurvaExecucaoPonto> _lstPontosBaseBullet;
        IList<CurvaExecucaoPonto> _curvaValorPresente;
        CalculosPMT _calculos;
        CurvaBS _curvaBS;
        ExecucaoCurvaBS _execucaoCurvaBS;
        ConversorTaxas _conversor;
        IList<TipoPrazo> tiposPrazo;

        #endregion

        #region Propriedades

        public List<Item> MapaCofiguracoesGerador
        {
            get;
            set;
        }

        public RegistroExecucaoCurva RegistroExecucaoCurva
        {
            get;
            set;
        }

        public Curva Curva
        {
            get;
            set;
        }

        public int SituacaoAtual
        {
            get;
            set;
        }

        #endregion

        #region Construtor

        public GeradorDeCurvaPMT()
        {
            _calculos = new CalculosPMT();
            _curvaBS = new CurvaBS();
            _execucaoCurvaBS = new ExecucaoCurvaBS();
            _conversor = new ConversorTaxas();
            tiposPrazo = _curvaBS.ObterTiposPrazo();
        }

        #endregion

        #region Destrutor

        ~GeradorDeCurvaPMT()
        {
            _calculos = null;
            _curvaBS = null;
            _execucaoCurvaBS = null;
            _conversor = null;
            tiposPrazo = null;
        }

        #endregion

        #region Método Público

        public void Gerar()
        {
            try
            {
                GerarCurvaPMTPre();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        #endregion

        #region Métodos Privados

        private void GerarCurvaPMTPre()
        {
            try
            {
                AtualizarSituacaoExecucaoCurvaParaEmExecucao();

                ObterDadosCurvaBullet();

                ObterAnosExtensaoCurvaBullet();

                ConverterCurvaBullet();

                MontarFluxo();

                CalcularTIR();

                GravarPontos();
            }
            catch (Exception ex)
            {
                LimparSujeiraExecucao();
                Exception exCurva = new Exception(string.Format("Não foi possível gerar a curva PMT Pré de código: {0} ({1}) e nome: {2}.", RegistroExecucaoCurva.CodigoCurva, RegistroExecucaoCurva.DataHoraExecucaoCurva.ToString(MASCARA_DATA), Curva.NomeCurva), ex);
                EnterpriseLibraryFactory.GetExceptionHandler().HandleException(exCurva, "ErrosGerador");
            }
        }

        private void ObterAnosExtensaoCurvaBullet()
        {
            ComposicaoCurva composicaoCurvaComposta = _curvaBS.ObterCurvaComposicaoVigente(_curvaBaseBullet.CodigoCurva, _curvaBaseBullet.CurvaVigente.DataInclusao);

            int anosExtensaoResultado = 0;

            for (int cont = 0; cont < composicaoCurvaComposta.Itens.Count; cont++)
            {
                int anosExtensao = _curvaBS.ObterCurva(composicaoCurvaComposta.Itens[cont].CodigoCurvaIntegrante).QuantidadeAnoExtensao;

                if ((anosExtensaoResultado == 0) || (anosExtensaoResultado > anosExtensao))
                {
                    anosExtensaoResultado = anosExtensao;
                }
            }

            if (anosExtensaoResultado > 0)
            {
                _curvaBaseBullet.QuantidadeAnoExtensao = anosExtensaoResultado;
            }
            else if (_lstPontosBaseBullet != null && _lstPontosBaseBullet.Count > 0)
            {
                int resto = 0;
                _curvaBaseBullet.QuantidadeAnoExtensao = Math.DivRem(_lstPontosBaseBullet[_lstPontosBaseBullet.Count - 1].QuantidadeDiasCorridos, 365, out resto);
            }
        }

        private void LimparSujeiraExecucao()
        {
            RegistroExecucaoCurva.CodigoSituacao = SituacaoAtual;
            FilaExcecao.Adicionar(RegistroExecucaoCurva);
        }

        private void AtualizarSituacaoExecucaoCurvaParaEmExecucao()
        {
            SituacaoAtual = RegistroExecucaoCurva.CodigoSituacao;
            AlterarSituacaoDaExecucao(SITUACAO_EM_EXECUCAO + SituacaoAtual);
            RegistroExecucaoCurva.CodigoSituacao = SituacaoAtual;
            Logger.GravarLog(string.Format("Processando a curva de código: {0} e nome: {1}", RegistroExecucaoCurva.CodigoCurva, Curva.NomeCurva));
        }

        private void AlterarSituacaoDaExecucao(int codigoSituacao)
        {
            RegistroExecucaoCurva.CodigoSituacao = codigoSituacao;
            _execucaoCurvaBS.AtualizarRegistroExecucaoCurva(RegistroExecucaoCurva);
        }

        private void GravarPontos()
        {
            RegistroExecucaoCurva.CodigoSituacao = SITUACAO_LIBERADA;
            _execucaoCurvaBS.PersistirExecucao(_curvaValorPresente, RegistroExecucaoCurva);
        }

        private void ConverterCurvaBullet()
        {
            TaxaPontoCurvaVigente tipoVertice = _curvaBaseBullet.CurvaVigente.lstTaxaPontoCurvaVigente.FirstOrDefault(t => t.CodigoTipoPontoCurva.Equals(TIPO_PONTO_VERTICE));

            if (tipoVertice != null && tipoVertice.CodigoCurva > 0)
            {
                if (tipoVertice.CodigoTipoTaxa.Equals(TIPO_TAXA_TAXA))
                {
                    if (_curvaBaseBullet.CurvaVigente.CodigoTipoPrazo.Equals(TIPOPRAZO_OVER))
                    {
                        _lstPontosBaseBullet = _conversor.ConverterTaxasOverParaFatorDiario(_lstPontosBaseBullet);
                    }
                    else
                    {
                        if (_curvaBaseBullet.CurvaVigente.CodigoTipoTaxa == TIPO_TAXA_EXPONENCIAL)
                        {
                            _lstPontosBaseBullet = _conversor.ConverterTaxasExponenciaisParaFatorDiario(_lstPontosBaseBullet, tiposPrazo.Where(c => c.Codigo == _curvaBaseBullet.CurvaVigente.CodigoTipoPrazo).ToList()[0].QuantidadeDiasAno);
                        }
                        else if (_curvaBaseBullet.CurvaVigente.CodigoTipoTaxa == TIPO_TAXA_LINEAR)
                        {
                            _lstPontosBaseBullet = _conversor.ConverterTaxasLinearesParaFatorDiario(_lstPontosBaseBullet, tiposPrazo.Where(c => c.Codigo == _curvaBaseBullet.CurvaVigente.CodigoTipoPrazo).ToList()[0].QuantidadeDiasAno);
                        }
                    }
                }
                else if (tipoVertice.CodigoTipoTaxa.Equals(TIPO_TAXA_PU))
                {
                    _lstPontosBaseBullet = _conversor.ConverterPUsParaFatorDiario(_lstPontosBaseBullet);
                }
            }
        }

        private double ConverterValorTIR(double valorTIR, int dias, int baseDias)
        {
            double resultado = 0;

            TaxaPontoCurvaVigente tipoVertice = _curvaBaseBullet.CurvaVigente.lstTaxaPontoCurvaVigente.FirstOrDefault(t => t.CodigoTipoPontoCurva.Equals(TIPO_PONTO_VERTICE));

            if (tipoVertice != null && tipoVertice.CodigoCurva > 0)
            {
                if (tipoVertice.CodigoTipoTaxa.Equals(TIPO_TAXA_TAXA)
                    || tipoVertice.CodigoTipoTaxa.Equals(TIPO_TAXA_FATOR_DIARIO))
                {
                    if (_curvaBaseBullet.CurvaVigente.CodigoTipoPrazo.Equals(TIPOPRAZO_OVER))
                    {
                        resultado = _conversor.ConverterTaxaOverParaFatorDiario(valorTIR, dias);
                    }
                    else
                    {
                        if (_curvaBaseBullet.CurvaVigente.CodigoTipoTaxa == TIPO_TAXA_EXPONENCIAL)
                        {
                            resultado = _conversor.ConverterTaxaExponencialParaFatorDiario(valorTIR, baseDias, dias);
                        }
                        else if (_curvaBaseBullet.CurvaVigente.CodigoTipoTaxa == TIPO_TAXA_LINEAR)
                        {
                            resultado = _conversor.ConverterTaxaLinearParaFatorDiario(valorTIR, baseDias, dias);
                        }
                    }
                }
                else if (tipoVertice.CodigoTipoTaxa.Equals(TIPO_TAXA_PU))
                {
                    resultado = _conversor.ConverterPUParaFatorDiario(valorTIR);
                }
            }

            return resultado;
        }

        private void MontarFluxo()
        {
            _curvaValorPresente = new List<CurvaExecucaoPonto>();
            DateTime dataVencimento = RegistroExecucaoCurva.DataHoraExecucaoCurva;

            _reguaCurva = new CalendarioReguaPMTBS(dataVencimento, _curvaBaseBullet.QuantidadeAnoExtensao);

            foreach (KeyValuePair<string, DiaInfo> chave in _reguaCurva.Regua)
            {
                var ponto = _lstPontosBaseBullet.FirstOrDefault(p => p.QuantidadeDiasCorridos == chave.Value.DiasCorridos);

                if (ponto != null && ponto.CodigoCurva > 0)
                {
                    CurvaExecucaoPonto pontoCurva = new CurvaExecucaoPonto();

                    pontoCurva.QuantidadeDiasCorridos = chave.Value.DiasCorridos;
                    pontoCurva.CodigoCurva = RegistroExecucaoCurva.CodigoCurva;
                    pontoCurva.DataInclusaoVigencia = RegistroExecucaoCurva.DataInclusaoVigencia;
                    pontoCurva.DataHoraExecucaoCurva = RegistroExecucaoCurva.DataHoraExecucaoCurva;
                    pontoCurva.DataVencimento = RegistroExecucaoCurva.DataHoraExecucaoCurva.AddDays(chave.Value.DiasCorridos);
                    pontoCurva.QuantidadeDiasUteis = chave.Value.DiasUteis;
                    pontoCurva.IndicadorVertice = INDICADOR_VERTICE_SIM;

                    pontoCurva.ValorVertice = _calculos.CalcularValorPresente(1, ponto.ValorVertice);
                    _curvaValorPresente.Add(pontoCurva);
                }
            }
        }

        protected void ObterDadosCurvaBullet()
        {
            _curvaBaseBullet = _curvaBS.ObterCurva(Curva.CurvaVigente.CodigoCurvaBasePMT);

            _registroExecucaoCurvaBaseBullet = _execucaoCurvaBS.ObterRegistroExecucaoCurva(_curvaBaseBullet.CurvaVigente.CodigoCurvaVigente,
                _curvaBaseBullet.CurvaVigente.DataInclusao, RegistroExecucaoCurva.DataHoraExecucaoCurva);

            if (_registroExecucaoCurvaBaseBullet.CodigoCurva == 0)
            {
                _registroExecucaoCurvaBaseBullet = _execucaoCurvaBS.ObterUltimaExecucaoCurva(_curvaBaseBullet.CurvaVigente.CodigoCurvaVigente,
                    _curvaBaseBullet.CurvaVigente.DataInclusao, RegistroExecucaoCurva.DataHoraExecucaoCurva.Date.AddHours(23).AddMinutes(59).AddSeconds(59), SITUACAO_LIBERADA);
            }

            if (_registroExecucaoCurvaBaseBullet.CodigoSituacao == SITUACAO_LIBERADA
                && RegistroExecucaoCurva.DataHoraExecucaoCurva.Date == _registroExecucaoCurvaBaseBullet.DataHoraExecucaoCurva.Date)
            {
                _lstPontosBaseBullet = _execucaoCurvaBS.ObterVerticesCurvaVigente(_curvaBaseBullet.CurvaVigente.CodigoCurvaVigente,
                    _registroExecucaoCurvaBaseBullet.DataInclusaoVigencia, _registroExecucaoCurvaBaseBullet.DataHoraExecucaoCurva);
            }
            else 
            {
                throw new Exception(string.Format("Não foi possível obter a execução da curva Bullet. Código da curva bullet: {0}. Data inclusão vigência bullet: {1}. Data hora execução bullet: {2}. Código da curva PMT: {3}. Data inclusão vigência PMT: {4}. Data hora execução PMT: {5}", _registroExecucaoCurvaBaseBullet.CodigoCurva, _registroExecucaoCurvaBaseBullet.DataInclusaoVigencia, _registroExecucaoCurvaBaseBullet.DataHoraExecucaoCurva, RegistroExecucaoCurva.CodigoCurva, RegistroExecucaoCurva.DataInclusaoVigencia, RegistroExecucaoCurva.DataHoraExecucaoCurva));
            }
        }

        private void CalcularTIR()
        {
            int baseDias = RetornarBaseDias(_curvaBaseBullet.CurvaVigente);
            List<double> valoresTIR = new List<double>();
            double somaVP = 0;
            int somaDias = 0;

            // Iteração para cada parcela (Resultado é a TIR da Parcela)
            for (int i = 0; i < _curvaValorPresente.Count; i++)
            {
                double VLP = 0;
                int dias = RetornarQuantidadeDias(_curvaBaseBullet.CurvaVigente, _curvaValorPresente[i]);
                double estimativaTIRInicial = 0;
                double estimativaTIR = 0;
                double diferencaSomaVPEstimativaTIR = 0;
                double diferencaSomaVPEstimativaTIRAnterior = 0;
                int contadorParcelas = i + 1;
                int contadorEstimativas = 1;

                somaVP += _curvaValorPresente[i].ValorVertice;
                somaDias += RetornarQuantidadeDias(_curvaBaseBullet.CurvaVigente, _curvaValorPresente[i]);

                estimativaTIRInicial = _calculos.CalcularTIR(somaDias, somaVP, contadorParcelas, contadorParcelas, baseDias);

                for (int j = 0; j <= i; j++)
                {
                    VLP += _calculos.CalcularValorPresente(1, ConverterValorTIR(estimativaTIRInicial * 100, RetornarQuantidadeDias(_curvaBaseBullet.CurvaVigente, _curvaValorPresente[j]), baseDias));
                }

                diferencaSomaVPEstimativaTIR = somaVP - VLP;

                if (Math.Round(diferencaSomaVPEstimativaTIR, 10) == 0.0)
                {
                    estimativaTIR = estimativaTIRInicial;
                }

                // Iteração das estimativas da TIR (Resultado é a determinação da TIR)
                while ((Math.Round(diferencaSomaVPEstimativaTIR, 10) > 0.0 || (Math.Round(diferencaSomaVPEstimativaTIR, 10) < 0.0))
                    && contadorEstimativas <= 15
                    && diferencaSomaVPEstimativaTIR != diferencaSomaVPEstimativaTIRAnterior)
                {
                    VLP = 0;

                    if (contadorEstimativas == 1)
                    {
                        if (diferencaSomaVPEstimativaTIR < 0)
                        {
                            estimativaTIR = estimativaTIRInicial * 2;
                        }
                        else
                        {
                            estimativaTIR = (double)estimativaTIRInicial / 2;
                        }
                    }
                    else
                    {
                        double reserva = estimativaTIR;
                        estimativaTIR = _calculos.AproximarValorTIR(estimativaTIRInicial, estimativaTIR, diferencaSomaVPEstimativaTIR, diferencaSomaVPEstimativaTIRAnterior);
                        estimativaTIRInicial = reserva;
                    }

                    // Iteração do Fluxo tendo com o resultado um valor VP mais próximo
                    for (int j = 0; j <= i; j++)
                    {
                        VLP += _calculos.CalcularValorPresente(1, ConverterValorTIR(estimativaTIR * 100, RetornarQuantidadeDias(_curvaBaseBullet.CurvaVigente, _curvaValorPresente[j]), baseDias));
                    }

                    diferencaSomaVPEstimativaTIRAnterior = diferencaSomaVPEstimativaTIR;
                    diferencaSomaVPEstimativaTIR = somaVP - VLP;
                    contadorEstimativas++;
                }

                valoresTIR.Add(estimativaTIR);
            }

            // Repassando os valores da TIR para a curva
            for (int i = 0; i < _curvaValorPresente.Count; i++)
            {
                _curvaValorPresente[i].ValorVertice = valoresTIR[i] * 100;
            }
        }

        private int RetornarQuantidadeDias(CurvaVigente _curvaVigenteBaseBullet, CurvaExecucaoPonto ponto)
        {
            if (_curvaVigenteBaseBullet.CodigoTipoPrazo.Equals(TIPOPRAZO_252))
            {
                return ponto.QuantidadeDiasUteis;
            }
            else
            {
                return ponto.QuantidadeDiasCorridos;
            }
        }

        private int RetornarQuantidadeDias(CurvaVigente _curvaVigenteBaseBullet, KeyValuePair<string, DiaInfo> chave)
        {
            if (_curvaVigenteBaseBullet.CodigoTipoPrazo.Equals(TIPOPRAZO_252))
            {
                return chave.Value.DiasUteis;
            }
            else
            {
                return chave.Value.DiasCorridos;
            }
        }

        private int RetornarBaseDias(CurvaVigente _curvaVigenteBaseBullet)
        {
            if (_curvaVigenteBaseBullet.CodigoTipoPrazo.Equals(TIPOPRAZO_30)
                || _curvaVigenteBaseBullet.CodigoTipoPrazo.Equals(TIPOPRAZO_OVER))
            {
                return 30;
            }
            else if (_curvaVigenteBaseBullet.CodigoTipoPrazo.Equals(TIPOPRAZO_360))
            {
                return 360;
            }
            else
            {
                return 252;
            }
        }

        #endregion

    }
}
